 /*
 * ObjModel.java
 * Author: Colin Ponce
 * Written: 9/12/2010
 *
 * With code taken from http://www.openprocessing.org/visuals/?visualID=191#
*/

/**
 * @author Colin Ponce
 * @author Jeran Fox
 * 
 * 2010-09-12
 */

package geometry;

import geometry.TriangleMesh.TriangleIndex;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.media.opengl.GL2;
import javax.vecmath.AxisAngle4f;
import javax.vecmath.Color3f;
import javax.vecmath.Color4f;
import javax.vecmath.Matrix4f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.TexCoord2f;
import javax.vecmath.Vector3f;

import util.Util;
import collision.BoundingBox;
import collision.BoundingSphere;
 
public class ObjModel extends Drawable
{
	protected Color4f _color;
	protected String _fileName;
	protected String _mtlName;
	protected Material _currentMaterial;
	protected ArrayList<Point3f> _vertices;
	protected ArrayList<Vector3f> _normals;
	protected ArrayList<TexCoord2f> _textures;
	protected ArrayList<Polygon> _polygons;
	protected float _scale;
	protected Vector3f _translate;
	protected AxisAngle4f _rotation;
	protected Matrix4f _transform;
	
	protected BoundingBox _boundingBox;
	protected BoundingSphere _boundingSphere;
	protected TriangleMesh _triangleMesh;
	
	class Material
	{
		public float[] _ka;
		public float[] _kd;
		public float[] _ks;
		public float _sh;
		
		public Material()
		{
			float[] ambient = {0.2f, 0.2f, 0.2f, 1.0f};
			float[] diffuse = {0.8f, 0.8f, 0.8f, 1.0f};
			float[] specular = {0.0f, 0.0f, 0.0f, 1.0f};
			float shininess = 0.0f;
			
			_ka = ambient;
			_kd = diffuse;
			_ks = specular;
			_sh = shininess;
		}
		
		public Material(float[] ambient_, float[] diffuse_, float[] specular_, float shininess_)
		{
			_ka = ambient_;
			_kd = diffuse_;
			_ks = specular_;
			_sh = shininess_;
		}
		
		public Material clone()
		{
			return new Material(_ka, _kd, _ks, _sh);
		}
	}
	
	public ObjModel(String fn_)
	{
		this(fn_, null, 1, null, null);
	}
	
	public ObjModel(String fn_, String mtlName_)
	{
		this(fn_, mtlName_, 1, null, null);
	}
	
	public ObjModel(String fn_, String mtlName_, float scale_, Vector3f translation_, AxisAngle4f rotation_)
	{
		_fileName = fn_;
		_mtlName = mtlName_;
		_vertices = new ArrayList<Point3f>();
		_normals = new ArrayList<Vector3f>();
		_textures = new ArrayList<TexCoord2f>();
		_polygons = new ArrayList<Polygon>();
		_transform = new Matrix4f();
		scale(scale_);
		translate(translation_);
		rotate(rotation_);
		
		readFile();
		
		initBoundingBox();
		initBoundingSphere();
		initTriangleMesh();
		
		//init colors
		_color = new Color4f(.5f, .5f, .5f, 1f);
		super._color = new Color3f(.5f, .5f, .5f);
	}
	
	private void scale(float scale_)
	{
		_scale = scale_;
		_transform.setScale(_scale);
	}
	
	private void translate(Vector3f t_)
	{
		if(t_ != null)
		{
			_translate = t_;
			_transform.setTranslation(_translate);
		}
	}
	
	private void rotate(AxisAngle4f r_)
	{
		if(r_ != null)
		{
			_rotation = r_;
			_transform.setRotation(r_);
		}
	}
	
	private void initBoundingBox()
	{
		_boundingBox = _polygons.get(0).getBoundingBox().clone();
		for (int i = 1; i < _polygons.size(); i++)
			_boundingBox.extend(_polygons.get(i).getBoundingBox());
		_boundingBox._object = this;
	}
	
	private void initBoundingSphere()
	{
//		Point3d origin = new Point3d();
//		origin.x = (_boundingBox._minPt.x + _boundingBox._maxPt.x) / 2;
//		origin.y = (_boundingBox._minPt.y + _boundingBox._maxPt.y) / 2;
//		origin.z = (_boundingBox._minPt.z + _boundingBox._maxPt.z) / 2;
//		double radius = -1;
//		for (Point3f p : _vertices)
//		{
//			double d = Util.distance(origin, new Point3d(p.x, p.y, p.z));
//			if (d > radius) radius = d;
//		}
		_boundingSphere = new BoundingSphere(this, this);
	}
	
	public BoundingBox getBoundingBox()
	{
		return _boundingBox;
	}
	
	public BoundingSphere getBoundingSphere()
	{
		return _boundingSphere;
	}
	
	private void initTriangleMesh()
	{
		List<TriangleIndex> triangles = new ArrayList<TriangleIndex>();
		List<Point3d> vertices = new ArrayList<Point3d>();
		
		for(Polygon p: _polygons)
		{
			int indexStartFan = _vertices.indexOf(p._vertices.get(0));
			for(int i = 2; i < p._vertices.size(); i++)
			{
				int indexb = _vertices.indexOf(p._vertices.get(i - 1));
				int indexc = _vertices.indexOf(p._vertices.get(i));
				triangles.add(new TriangleIndex(indexStartFan, indexb, indexc));
			}
		}
		
		for(Point3f v : _vertices)
			vertices.add(new Point3d(v.x, v.y, v.z));
		_triangleMesh = new TriangleMesh(vertices, triangles);
	}
	
	@Override
	public TriangleMesh getTriangleMesh()
	{	
		return _triangleMesh;
	}
	
	@Override
	public void build(GL2 gl) {
		_listNum = gl.glGenLists(1);
		gl.glNewList(_listNum, GL2.GL_COMPILE);
		
		gl.glPushMatrix();

		gl.glColor4f(_color.x, _color.y, _color.z, 1);

		for (int i = 0; i < _polygons.size(); i++)
			_polygons.get(i).draw(gl);
		
		gl.glPopMatrix();
		
		gl.glEndList();
	}
	
	public List<Polygon> getPolygons()
	{
		return _polygons;
	}
	
	public Polygon getPolygon(int index) { return _polygons.get(index); }
	public int numPolygons() { return _polygons.size(); }
	
	private Point3f getVertex(int index)
	{
		if (index >= 0) return _vertices.get(index);
		else return _vertices.get(_vertices.size() + index);
	}
	
	private Vector3f getNormal(int index)
	{
		if (index >= 0) return _normals.get(index);
		else return _normals.get(_normals.size() + index);
	}
	
	private TexCoord2f getTexture(int index)
	{
		if (index >= 0) return _textures.get(index);
		else return _textures.get(_textures.size() + index);
	}

	private void readFile() {
		if (_mtlName != null)
			_currentMaterial = new Material();
		try{
			BufferedReader input = new BufferedReader(new FileReader(_fileName));
			try{
				String newLine = null;
				while((newLine = input.readLine()) != null){
					int ind = newLine.indexOf("usemtl ");
					if(ind != -1){
						if(_mtlName != null)
							readMaterial(newLine);
						continue;
					}
					
					ind = newLine.indexOf("vn ");
					if(ind != -1){
						readNormal(newLine);
						continue;
					}

					ind = newLine.indexOf("v ");
					if(ind != -1){
						readVertex(newLine);
						continue;
					}

					ind = newLine.indexOf("f ");
					if(ind != -1) {
						readPolygon(newLine);
						continue;
					} 

					ind = newLine.indexOf("vt ");
					if(ind != -1){
						readTexture(newLine);
						continue;
					}
				}
			}
			finally{
				input.close();
			}
		}
		catch(IOException ex){
			ex.printStackTrace();
		}
	}

	private void readVertex(String newLine){
		String pieces[] = newLine.split("\\s+");
		Point3f vertex = new Point3f(Float.parseFloat(pieces[1]), Float.parseFloat(pieces[2]), Float.parseFloat(pieces[3]));
		_transform.transform(vertex);
		_vertices.add(vertex);
	}

	private void readNormal(String newLine){
		String pieces[] = newLine.split("\\s+");
		Vector3f norm = new Vector3f(Float.parseFloat(pieces[1]), Float.parseFloat(pieces[2]), Float.parseFloat(pieces[3]));
		_transform.transform(norm);
		_normals.add(norm);
	}

	private void readTexture(String newLine){
		String pieces[] = newLine.split("\\s+");
		TexCoord2f tex = new TexCoord2f(Float.parseFloat(pieces[1]), Float.parseFloat(pieces[2]));
		_textures.add(tex);
	}
	
	private void readMaterial(String newLine_){
		String material = newLine_.substring(newLine_.indexOf(" ") + 1);
		try{
			BufferedReader input = new BufferedReader(new FileReader(_mtlName));
			try{
				String newLine = null;
				boolean materialFound = false;
				while((newLine = input.readLine()) != null){
					int ind = newLine.indexOf("newmtl ");
					if(ind != -1){
						ind = newLine.indexOf(material);
						if(ind != -1)
							materialFound = true;
						else
							materialFound = false;
						continue;
					}
					
					if(materialFound)
					{
						ind = newLine.indexOf("ka ");
						if(ind != -1) {
							String pieces[] = newLine.split("\\s+");
							float[] ka = {Float.parseFloat(pieces[1]), Float.parseFloat(pieces[2]), Float.parseFloat(pieces[3]), Float.parseFloat(pieces[4])};
							_currentMaterial._ka = ka;
							continue;
						} 

						ind = newLine.indexOf("kd ");
						if(ind != -1){
							String pieces[] = newLine.split("\\s+");
							float[] kd = {Float.parseFloat(pieces[1]), Float.parseFloat(pieces[2]), Float.parseFloat(pieces[3]), Float.parseFloat(pieces[4])};
							_currentMaterial._kd = kd;
							continue;
						}
						
						ind = newLine.indexOf("ks ");
						if(ind != -1){
							String pieces[] = newLine.split("\\s+");
							float[] ks = {Float.parseFloat(pieces[1]), Float.parseFloat(pieces[2]), Float.parseFloat(pieces[3]), Float.parseFloat(pieces[4])};
							_currentMaterial._ks = ks;
							continue;
						}
						
						ind = newLine.indexOf("Ns ");
						if(ind != -1){
							String pieces[] = newLine.split("\\s+");
							_currentMaterial._sh = Float.parseFloat(pieces[1]);
							continue;
						}
						//ignore other mtl attributes for now
					}
				}
			}
			finally{
				input.close();
			}
		}
		catch(IOException ex){
			ex.printStackTrace();
		}
	}

	private void readPolygon(String newLine) throws IOException {  // assumes we're reading a triangle
		String pieces[] = newLine.split("\\s+");
		Polygon poly = new Polygon();
		for(int i = 1; i < pieces.length; i++){
			String smallerPieces[] = pieces[i].split("//");
			boolean hasTexture = false;
			if (smallerPieces.length == 1) {
				smallerPieces = pieces[i].split("/");
				hasTexture = true;
			}
			
			if(smallerPieces.length == 3){ // we have verts/texture/normals
				poly._normals.add(getNormal(Integer.parseInt(smallerPieces[2])-1));
				poly._textures.add(getTexture(Integer.parseInt(smallerPieces[1])-1));
			}
			else if (hasTexture && smallerPieces.length == 2) { // only vertex/texture
				poly._textures.add(getTexture(Integer.parseInt(smallerPieces[1])-1));
			}
			else if (smallerPieces.length == 2)
				poly._normals.add(getNormal(Integer.parseInt(smallerPieces[1])-1)); // only vertex/normal

			poly._vertices.add(getVertex(Integer.parseInt(smallerPieces[0])-1));
		}
		if(_currentMaterial != null)
			poly.setMaterial(_currentMaterial);
		_polygons.add(poly);
	}		
}